#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <errno.h>

#include "librc.h"

// TO DO: need a proper response parser, sscanf is bad

#define BUFSZ 2048

modem_signal_t *modem_get_signal(int fd)
{
	modem_signal_t *signal = NULL;
	char *buf;
	int rat = -1;
	
	do {
		if (!(buf = malloc(BUFSZ)))
			break;
		if (!(signal = malloc(sizeof(modem_signal_t))) ||
		     !memset(signal, 0, sizeof(modem_signal_t)))
			break;
		signal->sinr = 99;
		if (atcommand(fd, "AT+CSQ", buf, BUFSZ, 1))
			break;
		if (sscanf(buf, "+CSQ: %d,%*d", &signal->rssi) < 1)
			break;
		if (signal->rssi == 99)
			signal->rssi = 0;
		else
			signal->rssi = -113 + signal->rssi * 2;
		if (!atcommand(fd, "AT+COPS?", buf, BUFSZ, 1))
			sscanf(buf, "+COPS: %*d,%*d,\"%*16[^\"\r\n]\",%d", &rat);
		if (rat == RAT_UTRAN && !atcommand(fd, "AT^CSNR?", buf, BUFSZ, 1))
			sscanf(buf, "^CSNR:%d,%d", &signal->rscp, &signal->ecio);
		if (rat == RAT_EUTRAN && !atcommand(fd, "AT^LTERSRP?", buf, BUFSZ, 1)) {
			sscanf(buf, "^LTERSRP:%d,%d", &signal->rsrp, &signal->rsrq);
			if (!atcommand(fd, "AT^LTESCINFO?", buf, BUFSZ, 1))
				sscanf(buf, "^LTESCINFO:%*d,%d", &signal->sinr);
			// 21...25 -> -5...-1 dB
			if (signal->sinr >= 21 && signal->sinr <= 25)
				signal->sinr -= 26;
		}
		if (!atcommand(fd, "AT*QRFINFO?", buf, BUFSZ, 1)) {
			char sysmode[32];
			int sig, qual, sinr;
			if (sscanf(buf, "*QRFINFO: \"%31[^\"\r\n]\",%*d,%*d,%*d,%*d,%d,%d,%d.%*d", sysmode, &sig, &qual, &sinr) == 4) {
				if (!strcmp(sysmode, "GSM"))
					signal->rssi = sig;
				else if (!strcmp(sysmode, "WCDMA")) {
					signal->rscp = sig;
					signal->ecio = sinr;
				}
				else if (!strcmp(sysmode, "LTE")) {
					signal->rsrp = sig;
					signal->rsrq = qual;
					signal->sinr = sinr;
				}
			}
		}
	} while (0);

	free(buf);
	return signal;
}

int modem_check_pin_request(int fd)
{
	char sreq[17];
	int ireq = -1;
	char *buf;

	do {
		if (!(buf = malloc(BUFSZ)))
			break;
		if (atcommand(fd, "AT+CPIN?", buf, BUFSZ, 1))
			break;
		if (sscanf(buf, "+CPIN: %16[^\r\n]", sreq) < 1)
			break;
		if (!strcmp(sreq, "READY"))
			ireq = MODEM_PIN_READY;
		else if (!strcmp(sreq, "SIM PIN"))
			ireq = MODEM_SIM_PIN;
		else if (!strcmp(sreq, "SIM PUK"))
			ireq = MODEM_SIM_PUK;
		else if (!strcmp(sreq, "SIM PIN2"))
			ireq = MODEM_SIM_PIN2;
		else if (!strcmp(sreq, "SIM PUK2"))
			ireq = MODEM_SIM_PUK2;
	} while (0);

	free(buf);
	return ireq;
}

int modem_enter_pin(int fd, char *pin)
{
	char cmd[32];
	int ret = 1;
	char *buf;

	do {
		if (!(buf = malloc(BUFSZ)))
			break;
		if (!pin || strlen(pin) < 4 || strlen(pin) > 8)
			break;
		int req = modem_check_pin_request(fd);
		if (req != MODEM_SIM_PIN)
			break;
		snprintf(cmd, sizeof(cmd), "AT+CPIN=%s", pin);
		if (atcommand(fd, cmd, buf, BUFSZ, 1))
			break;
		ret = 0;
	} while (0);

	free(buf);
	return ret;
}

modem_netinfo_t *modem_get_netinfo(int fd)
{
	modem_netinfo_t *netinfo = NULL;
	static char oper[17], operrat[32], mcc[4], mnc[4];
	char *buf;

	oper[0] = '\0';
	operrat[0] = '\0';
	mcc[0] = '\0';
	mnc[0] = '\0';

	if (!(buf = malloc(BUFSZ)))
		goto fail;
	if (!(netinfo = malloc(sizeof(modem_netinfo_t))) ||
	     !memset(netinfo, 0, sizeof(modem_netinfo_t)))
		goto fail;
	netinfo->oper = oper;
	netinfo->operrat = operrat;
	netinfo->mcc = mcc;
	netinfo->mnc = mnc;
	if (atcommand(fd, "AT+CREG=2", 0, 0, 0))
		goto fail;
	if (atcommand(fd, "AT+CREG?", buf, BUFSZ, 1))
		goto fail;
	// at least registration status must be present, other info is optional
	// lac and ci may be quoted
	if (sscanf(buf, "+CREG: %*d,%d,%x,%x", &netinfo->stat, &netinfo->lac, &netinfo->ci) < 3 &&
		sscanf(buf, "+CREG: %*d,%d,\"%x\",\"%x\"", &netinfo->stat, &netinfo->lac, &netinfo->ci) < 1)
		goto fail;
	char *stat_s = "";
	switch (netinfo->stat) {
		case 0:
			stat_s = "Not registered";
			break;
		case 1:
			stat_s = "Home network";
			break;
		case 2:
			stat_s = "Searching..";
			break;
		case 3:
			stat_s = "Registration rejected";
			break;
		case 4:
			stat_s = "Unknown reason";
			break;
		case 5:
			stat_s = "Roaming network";
			break;
	}
	netinfo->stat_s = stat_s;
	if (atcommand(fd, "AT+COPS=3,2", 0, 0, 0))
		goto fail;
	if (atcommand(fd, "AT+COPS?", buf, BUFSZ, 1))
		goto fail;
	char mccmnc[7] = "";
	sscanf(buf, "+COPS: %*d,%*d,\"%6[^\"\r\n]\",%d", mccmnc, &netinfo->rat);
	strncpy(mcc, mccmnc, 3);
	strncpy(mnc, mccmnc + 3, 3);
	if (atcommand(fd, "AT+COPS=3,0", 0, 0, 0))
		goto fail;
	if (atcommand(fd, "AT+COPS?", buf, BUFSZ, 1))
		goto fail;
	sscanf(buf, "+COPS: %*d,%*d,\"%16[^\"\r\n]\",%d", oper, &netinfo->rat);
	char *rat_s = "";
	switch (netinfo->rat) {
		case RAT_GSM:
			rat_s = "2G";
			break;
		case RAT_UTRAN:
			rat_s = "3G";
			break;
		case RAT_EUTRAN:
			rat_s = "4G";
			break;
	}
	if (strlen(oper) > 0)
		snprintf(operrat, sizeof(operrat), "%s%s%s%s", oper, *rat_s ? " (" : "", rat_s, *rat_s ? ")" : "");
	goto end;

fail:
	free(netinfo);
	netinfo = NULL;

end:
	free(buf);
	return netinfo;
}

char *modem_get_model(int fd)
{
	char manuf[128], model[128];
	static char modem_s[256];// = "";
	char *buf;
	
	do {
		manuf[0] = '\0';
		model[0] = '\0';
		modem_s[0] = '\0';
		if (!(buf = malloc(BUFSZ)))
			break;
		if (atcommand(fd, "AT+CGMI", buf, BUFSZ, 0))
			break;
		if (sscanf(buf, "+CGMI: \"%127[^\"\r\n]\"", manuf) < 1 &&
		    sscanf(buf, "%127[^\r\n]", manuf) < 1)
			break;
		if (atcommand(fd, "AT+CGMM", buf, BUFSZ, 0))
			break;
		if (sscanf(buf, "+CGMM: \"%127[^\"\r\n]\"", model) < 1 &&
		    sscanf(buf, "%127[^\r\n]", model) < 1)
			break;
		snprintf(modem_s, sizeof(modem_s), "%s%s%s", strcmp(manuf, "OK") ? manuf : "", strcmp(manuf, "OK") ? " " : "", strcmp(model, "OK") ? model : "");
	} while (0);

	free(buf);
	return modem_s;
}

/*
+COPS: (1,"MegaFon RUS","MegaFon","25002",0),(2,"MegaFon RUS","MegaFon","25002",2),(3,"Beeline","Beeline","25099",0),(3,"Beeline","Beeline","25099",2),(1,"Uraltel","Uraltel","25039",2),(3,"MTS-RUS","MTS","25001",2),(3,"MTS-RUS","MTS","25001",0),,(0,1,2,3,4),(0,1,2)
+COPS: ,,(0,1,2,3,4),(0,1,2)
*/

/*char *modem_get_ops(int fd)
{
	static char oplist[2048];
	char *buf;

	oplist[0] = '\0';
	if (!(buf = malloc(BUFSZ)))
		goto end;
	if (atcommand(fd, "AT+COPS=?", buf, BUFSZ, 1))
		goto end;
	
end:
	free(buf);
	return oplist;
}*/

modem_traf_t *modem_get_traffic_info(int fd)
{
	modem_traf_t *traf;
	char *buf;

	do {
		if (!(buf = malloc(BUFSZ)))
			break;
		if (!(traf = malloc(sizeof(modem_traf_t))) ||
		     !memset(traf, 0, sizeof(modem_traf_t)))
			break;
		if (atcommand(fd, "AT^DSFLOWQRY", buf, BUFSZ, 1))
			break;
		if (sscanf(buf, "^DSFLOWQRY:%x,%llx,%llx,%x,%llx,%llx", &traf->last_ds_time, &traf->last_tx_flow, &traf->last_rx_flow, &traf->total_ds_time, &traf->total_tx_flow, &traf->total_rx_flow) < 6)
			break;
		goto success;
	} while (0);

	free(buf);
	return NULL;

success:

	free(buf);
	return traf;
}

modem_sysinfo_t *modem_get_sysinfo(int fd)
{
	modem_sysinfo_t *sysinfo;
	static char sysmode[32] = "", submode[32] = "";
	char *buf;

	do {
		if (!(buf = malloc(BUFSZ)))
			break;
		if (!(sysinfo = malloc(sizeof(modem_sysinfo_t))) ||
		     !memset(sysinfo, 0, sizeof(modem_sysinfo_t)))
			break;
		sysinfo->sysmode_name = sysmode;
		sysinfo->submode_name = submode;
		if (atcommand(fd, "AT^SYSINFOEX", buf, BUFSZ, 1) == 0) {
			if (sscanf(buf, "^SYSINFOEX:%d,%d,%d,%d,,%d,\"%31[^\"\r\n]\",%d,\"%31[^\"\r\n]\"",
			    &sysinfo->srv_status, &sysinfo->srv_domain, &sysinfo->roam_status, &sysinfo->sim_state,
			    &sysinfo->sysmode, sysinfo->sysmode_name, &sysinfo->submode, sysinfo->submode_name) < 8 &&	
			    sscanf(buf, "^SYSINFOEX:%d,%d,%d,%d,,%d,%31[^,\r\n],%d,%31[^,\r\n]",
			    &sysinfo->srv_status, &sysinfo->srv_domain, &sysinfo->roam_status, &sysinfo->sim_state,
			    &sysinfo->sysmode, sysinfo->sysmode_name, &sysinfo->submode, sysinfo->submode_name) < 8)
				break;
		}
		else if (atcommand(fd, "AT^SYSINFO", buf, BUFSZ, 1) == 0) {
			if (sscanf(buf, "^SYSINFO:%d,%d,%d,%d,%d,,%d",
			    &sysinfo->srv_status, &sysinfo->srv_domain, &sysinfo->roam_status,
			    &sysinfo->sysmode, &sysinfo->sim_state, &sysinfo->submode) < 6)
				break;
			switch (sysinfo->sysmode) {
				case 0:
					sysinfo->sysmode_name = "NO SERVICE";
					break;
				case 1:
					sysinfo->sysmode_name = "AMPS";
					break;
				case 2:
					sysinfo->sysmode_name = "CDMA";
					break;
				case 3:
					sysinfo->sysmode_name = "GSM/GPRS";
					break;
				case 4:
					sysinfo->sysmode_name = "HDR";
					break;
				case 5:
					sysinfo->sysmode_name = "WCDMA";
					break;
				case 6:
					sysinfo->sysmode_name = "GPS";
					break;
				case 7:
					sysinfo->sysmode_name = "GSM/WCDMA";
					break;
				case 8:
					sysinfo->sysmode_name = "CDMA/HDR HYBRID";
					break;
				case 15:
					sysinfo->sysmode_name = "TD-SCDMA";
					break;
				default:
					sysinfo->sysmode_name = "";
					break;
			}

			switch (sysinfo->submode) {
				case 0:
					sysinfo->submode_name = "NO SERVICE";
					break;
				case 1:
					sysinfo->submode_name = "GSM";
					break;
				case 2:
					sysinfo->submode_name = "GPRS";
					break;
				case 3:
					sysinfo->submode_name = "EDGE";
					break;
				case 4:
					sysinfo->submode_name = "WCDMA";
					break;
				case 5:
					sysinfo->submode_name = "HSDPA";
					break;
				case 6:
					sysinfo->submode_name = "HSUPA";
					break;
				case 7:
					sysinfo->submode_name = "HSPA";
					break;
				case 8:
					sysinfo->submode_name = "TD-SCDMA";
					break;
				case 9:
					sysinfo->submode_name = "HSPA+";
					break;
				case 17:
					sysinfo->submode_name = "HSPA+(64QAM)";
					break;
				case 18:
					sysinfo->submode_name = "HSPA+(MIMO)";
					break;
				default:
					sysinfo->submode_name = "";
					break;
			}
		}
		else if (atcommand(fd, "AT+ZPAS?", buf, BUFSZ, 1) == 0) {
			if (sscanf(buf, "+ZPAS: \"%31[^\"\r\n]\"",
			    sysinfo->sysmode_name) < 1)
				break;
		}
		else if (atcommand(fd, "AT*QRFINFO?", buf, BUFSZ, 1) == 0) {
			int band = 0, chan = 0, bw = 0;
			if (sscanf(buf, "*QRFINFO: \"%31[^\"\r\n]\",%d,%d,%d",
			    sysinfo->sysmode_name, &band, &chan, &bw) < 4)
				break;
			snprintf(submode, sizeof(submode), "Band %d, channel %d", band, chan);
			if (bw)
				snprintfcat(submode, sizeof(submode), ", %d MHz", bw);
			
			
		}
		else
			break;
		goto success;
	} while (0);

	free(buf);
	return NULL;

success:

	free(buf);
	return sysinfo;
}
